package com.xinxin.blockchain.common.service;

import com.xinxin.base.entity.Claim;
import com.xinxin.base.enums.TokenTypeEnum;
import com.xinxin.base.exception.BusinessException;
import com.xinxin.base.service.ITokenService;
import com.xinxin.base.util.JsonUtil;
import com.xinxin.blockchain.constant.ErrorCode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.springframework.stereotype.Service;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;

/**
 * @author ys
 * @date 2021/5/21
 */
@Slf4j
@Service
public class TokenService implements ITokenService {

    // Token有效期 30分钟
    private static final long AUTHORIZATION_TOKEN_EXPIRE = 30 * 60 * 1000;
    // Refresh Token有效期 一天
    private static final long REFRESH_TOKEN_EXPIRE = 24 * 60 * 60 * 1000;

    @Override
    public String signature(String payload, String secret) {
        try {
            Mac mac = Mac.getInstance("HmacSHA256");
            SecretKeySpec secretKeySpec = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
            mac.init(secretKeySpec);
            byte[] bytes = mac.doFinal(payload.getBytes(StandardCharsets.UTF_8));
            return Base64.encodeBase64String(bytes);
        } catch (GeneralSecurityException e) {
            log.error("signature error: {}", e.getMessage());
            throw new BusinessException(ErrorCode.INTERNAL_SERVER_ERROR);
        }
    }

    @Override
    public String generateToken(TokenTypeEnum type, Long accountId, String secret) {
        Claim claim = generateClaim(type, accountId);
        return generateToken(claim, secret);
    }

    private Claim generateClaim(TokenTypeEnum type, Long accountId) {
        Claim claim = new Claim();
        claim.setType(type.name());
        claim.setAccountId(accountId);

        switch (type) {
            case AUTHORIZATION:
                claim.setExp(System.currentTimeMillis() + AUTHORIZATION_TOKEN_EXPIRE);
                break;
            case REFRESH:
                claim.setExp(System.currentTimeMillis() + REFRESH_TOKEN_EXPIRE);
                break;
            default:
        }

        return claim;
    }

    private String generateToken(Claim claim, String secret) {
        byte[] bytes = JsonUtil.writeValueAsBytes(claim);
        String payload = Base64.encodeBase64String(bytes);
        return payload + "." + signature(payload, secret);
    }

    @Override
    public Claim validClaim(TokenTypeEnum type, String token) {
        String[] parts = token.split("\\.");
        if (parts.length != 2) {
            throw new BusinessException(ErrorCode.UNAUTHORIZED);
        }

        String payload = parts[0];
        String signature = parts[1];

        Claim claim = JsonUtil.readValue(Base64.decodeBase64(payload), Claim.class);
        claim.setPayload(payload);
        claim.setSignature(signature);

        if (!type.name().equals(claim.getType())) {
            throw new BusinessException(ErrorCode.UNAUTHORIZED);
        }

        if (System.currentTimeMillis() > claim.getExp()) {
            throw new BusinessException(ErrorCode.UNAUTHORIZED);
        }

        return claim;
    }
}
